
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       bootloader.c
  * @author     baiyang
  * @date       2023-4-23
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "hw_config.h"
#include "bl_protocol.h"

#include <rtthread.h>
#include <rtdevice.h>
/*-----------------------------------macro------------------------------------*/
#define BOOTLOADER_RESERVATION_SIZE    (APP_LOAD_ADDRESS - SOC_FLASH_START_ADRESS)
#define APP_SIZE_MAX                   (SOC_FLASH_SIZE - (BOOTLOADER_RESERVATION_SIZE + APP_RESERVATION_SIZE))

/* context passed to cinit */
#if INTERFACE_USART
# define BOARD_INTERFACE_CONFIG_USART    NULL
#endif
#if INTERFACE_USB
# define BOARD_INTERFACE_CONFIG_USB      NULL
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
struct boardinfo board_info = {
    .board_type    = MB_BOARD_TYPE,
    .board_rev     = 0,
    .fw_size       = APP_SIZE_MAX,

    .extf_size     = 0,
};

/* State of an inserted USB cable */
static bool usb_connected = false;
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
uint32_t
board_get_devices(void)
{
    uint32_t devices = BOOT_DEVICES_SELECTION;

    if (usb_connected) {
        devices &= BOOT_DEVICES_FILTER_ONUSB;
    }

    return devices;
}

static bool
board_test_force_pin()
{
#if defined(BOARD_FORCE_BL_PIN_IN) && defined(BOARD_FORCE_BL_PIN_OUT)
    /* two pins strapped together */
    volatile unsigned samples = 0;
    volatile unsigned vote = 0;

    for (volatile unsigned cycles = 0; cycles < 10; cycles++) {
        rt_pin_write(BOARD_FORCE_BL_PIN_OUT, 1);

        for (unsigned count = 0; count < 20; count++) {
            if (rt_pin_read(BOARD_FORCE_BL_PIN_IN) != 0) {
                vote++;
            }

            samples++;
        }

        rt_pin_write(BOARD_FORCE_BL_PIN_OUT, 0);

        for (unsigned count = 0; count < 20; count++) {
            if (rt_pin_read(BOARD_FORCE_BL_PIN_IN) == 0) {
                vote++;
            }

            samples++;
        }
    }

    /* the idea here is to reject wire-to-wire coupling, so require > 90% agreement */
    if ((vote * 100) > (samples * 90)) {
        return true;
    }

#endif
#if defined(BOARD_FORCE_BL_PIN)
    /* single pin pulled up or down */
    volatile unsigned samples = 0;
    volatile unsigned vote = 0;

    for (samples = 0; samples < 200; samples++) {
        if ((rt_pin_read(BOARD_FORCE_BL_PIN) ? 1 : 0) == BOARD_FORCE_BL_STATE) {
            vote++;
        }
    }

    /* reject a little noise */
    if ((vote * 100) > (samples * 90)) {
        return true;
    }

#endif
    return false;
}

int bootloader_main(void)
{
    bool try_boot = true;            /* try booting before we drop to the bootloader */
    unsigned timeout = BOOTLOADER_DELAY;    /* if nonzero, drop out of the bootloader after this time */
  
    extern void sys_tick_handler(void);
    rt_tick_sethook(sys_tick_handler);

    led_init();

    /*
     * Check if the force-bootloader pins are strapped; if strapped,
     * don't try booting.
     */
    if (board_test_force_pin()) {
        try_boot = false;
    }

#if INTERFACE_USB

    /*
     * Check for USB connection - if present, don't try to boot, but set a timeout after
     * which we will fall out of the bootloader.
     *
     * If the force-bootloader pins are tied, we will stay here until they are removed and
     * we then time out.
     */
#if defined(BOARD_PIN_VBUS)

    if (rt_pin_read(BOARD_PIN_VBUS) != 0) {
        usb_connected = true;
        /* don't try booting before we set up the bootloader */
        try_boot = false;
    }

#else
    try_boot = false;

#endif
#endif

    /* start the interface */
#if INTERFACE_USART
    cinit(BOARD_INTERFACE_CONFIG_USART, USART);
#endif
#if INTERFACE_USB
    cinit(BOARD_INTERFACE_CONFIG_USB, USB);
#endif

    while (1) {
        /* run the bootloader, come back after an app is uploaded or we time out */
        bootloader(timeout);

        /* if the force-bootloader pins are strapped, just loop back */
        if (board_test_force_pin()) {
            continue;
        }

        /* set the boot-to-bootloader flag so that if boot fails on reset we will stop here */
#ifdef BOARD_BOOT_FAIL_DETECT
        board_set_rtc_signature(BOOT_RTC_SIGNATURE);
#endif

        /* look to see if we can boot the app */
        jump_to_app();

        /* launching the app failed - stay in the bootloader forever */
        timeout = 0;
    }
  
    return 0;
}

INIT_APP_EXPORT(bootloader_main);
/*------------------------------------test------------------------------------*/


